type StoreOptions = {
  keyPath?: string | string[] | null;
  autoIncrement?: boolean;
};

type StoreConfig = Record<string, StoreOptions>;

type IDBRequestMethod = 'add' | 'put' | 'delete' | 'get' | 'getAll' | 'clear';

type CursorDirection = 'next' | 'nextunique' | 'prev' | 'prevunique';

class WeIndexedDB {
  private dbName: string;
  private version: number;
  private db: IDBDatabase | null = null;

  /**
   * 构造函数
   * @param dbName 数据库名称
   * @param version 数据库版本
   */
  constructor(dbName: string, version: number) {
    this.dbName = dbName;
    this.version = version;
  }

  /**
   * 打开数据库
   * @param storeOptions 对象仓库配置 {storeName: {keyPath, autoIncrement}, ...}
   * @returns Promise<IDBDatabase>
   */
  async open(storeOptions: StoreConfig): Promise<IDBDatabase> {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, this.version);

      request.onerror = (event) => {
        reject(`打开数据库失败: ${(event.target as IDBRequest).error}`);
      };

      request.onsuccess = (event) => {
        this.db = (event.target as IDBOpenDBRequest).result;
        resolve(this.db);
      };

      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;

        for (const [storeName, options] of Object.entries(storeOptions)) {
          if (!db.objectStoreNames.contains(storeName)) {
            db.createObjectStore(storeName, options);
          }
        }
      };
    });
  }

  /**
   * 添加数据
   * @param storeName 对象仓库名称
   * @param data 要添加的数据
   * @returns Promise<IDBValidKey>
   */
  async add<T>(storeName: string, data: T): Promise<IDBValidKey> {
    return this._execute('add', storeName, data);
  }

  /**
   * 更新数据
   * @param storeName 对象仓库名称
   * @param data 要更新的数据
   * @returns Promise<IDBValidKey>
   */
  async put<T>(storeName: string, data: T): Promise<IDBValidKey> {
    return this._execute('put', storeName, data);
  }

  /**
   * 删除数据
   * @param storeName 对象仓库名称
   * @param key 要删除的数据键值
   * @returns Promise<void>
   */
  async delete(storeName: string, key: IDBValidKey): Promise<void> {
    return this._execute('delete', storeName, key);
  }

  /**
   * 获取数据
   * @param storeName 对象仓库名称
   * @param key 要获取的数据键值
   * @returns Promise<T | undefined>
   */
  async get<T>(storeName: string, key: IDBValidKey): Promise<T | undefined> {
    return this._execute('get', storeName, key);
  }

  /**
   * 获取所有数据
   * @param storeName 对象仓库名称
   * @returns Promise<T[]>
   */
  async getAll<T>(storeName: string): Promise<T[]> {
    return this._execute('getAll', storeName);
  }

  /**
   * 清空对象仓库
   * @param storeName 对象仓库名称
   * @returns Promise<void>
   */
  async clear(storeName: string): Promise<void> {
    return this._execute('clear', storeName);
  }

  /**
   * 执行游标查询
   * @param storeName 对象仓库名称
   * @param range 键范围
   * @param callback 每条记录的回调函数
   * @param direction 游标方向
   * @returns Promise<void>
   */
  async cursor<T>(
    storeName: string,
    range: IDBKeyRange | IDBValidKey | null,
    callback: (value: T, cursor: IDBCursorWithValue) => void,
    direction: CursorDirection = 'next'
  ): Promise<void> {
    return new Promise((resolve, reject) => {
      if (!this.db) {
        reject('数据库未打开');
        return;
      }

      const transaction = this.db.transaction(storeName, 'readonly');
      const store = transaction.objectStore(storeName);
      const request = store.openCursor(range, direction);

      request.onerror = (event) => {
        reject(`游标查询失败: ${(event.target as IDBRequest).error}`);
      };

      request.onsuccess = (event) => {
        const cursor = (event.target as IDBRequest<IDBCursorWithValue | null>).result;
        if (cursor) {
          callback(cursor.value as T, cursor);
          cursor.continue();
        } else {
          resolve();
        }
      };
    });
  }

  /**
   * 执行索引查询
   * @param storeName 对象仓库名称
   * @param indexName 索引名称
   * @param range 键范围
   * @returns Promise<T[]>
   */
  async getByIndex<T>(
    storeName: string,
    indexName: string,
    range?: IDBKeyRange | IDBValidKey | null
  ): Promise<T[]> {
    return new Promise((resolve, reject) => {
      if (!this.db) {
        reject('数据库未打开');
        return;
      }

      const transaction = this.db.transaction(storeName, 'readonly');
      const store = transaction.objectStore(storeName);
      const index = store.index(indexName);
      const request = index.getAll(range);

      request.onerror = (event) => {
        reject(`索引查询失败: ${(event.target as IDBRequest).error}`);
      };

      request.onsuccess = (event) => {
        resolve((event.target as IDBRequest<T[]>).result);
      };
    });
  }

  /**
   * 执行数据库操作
   */
  async _execute<T, U>(
    method: IDBRequestMethod,
    storeName: string,
    data?: U
  ): Promise<T> {
    return new Promise((resolve, reject) => {
      if (!this.db) {
        reject('数据库未打开');
        return;
      }

      const mode = method === 'get' || method === 'getAll' ? 'readonly' : 'readwrite';
      const transaction = this.db.transaction(storeName, mode);
      const store = transaction.objectStore(storeName);
      // @ts-ignore
      const request = store[method](data);

      request.onerror = (event) => {
        reject(`${method}操作失败: ${(event.target as IDBRequest).error}`);
      };

      request.onsuccess = (event) => {
        resolve((event.target as IDBRequest<T>).result);
      };
    });
  }

  /**
   * 关闭数据库连接
   */
  close(): void {
    if (this.db) {
      this.db.close();
      this.db = null;
    }
  }

  /**
   * 删除数据库
   * @param dbName 数据库名称
   * @returns Promise<void>
   */
  static async deleteDatabase(dbName: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const request = indexedDB.deleteDatabase(dbName);

      request.onerror = (event) => {
        reject(`删除数据库失败: ${(event.target as IDBRequest).error}`);
      };

      request.onsuccess = () => {
        resolve();
      };
    });
  }
}

export default WeIndexedDB
